#include "control_unit.h"

void control_unit::fsm(void)
{
  sc_lv<4> __s = 0b0000;

  while (true) {
    
    wait();

    /* RESET */
    if (rst.read() == SC_LOGIC_1) {
      _pc = 0;
      _ir = 0;
      _md = 0;
      _a = 0;
      _b = 0; 
      _alu_out = 0;
      _target = 0;

      for (int i = 0; i < N_REG; i++)
        _rf[i] = 0;

      __s = 0b0000;
      state.write(__s);

    } else {

      if (clk.read() == SC_LOGIC_1) {

        switch (state.read().to_uint()) {
          /* IDLE */
          case 0b0000:
          default:
            if (cc_rdy.read() == SC_LOGIC_1)
              __s = 0b0001;
            break;

          /* FETCH */
          case 0b0001:
            if (cc_rdy.read() == SC_LOGIC_1) {
              _pc = pc.read().to_uint() + 4; // next instruction
              _ir = cc_dout.read();
              __s = 0b0011;
            }
            break;

          /* DECODE */
          case 0b0011:
            _a = _rf[_ir.range(25, 21).to_uint()];
            _b = _rf[_ir.range(20, 16).to_uint()];
            _target = _pc.to_uint() + (_ir.range(15, 0).to_int() << 2);

            // switch opcode to decode instruction
            switch (_ir.range(31, 26).to_uint()) {

              // addi subi lw sw
              case 0b010001:
              case 0b010010:
              case 0b010011:
              case 0b010100:
                __s = 0b0111;
                break;

                // add sub slt sgt
              case 0b001001:
              case 0b001010:
              case 0b001011:
              case 0b001100:
                __s = 0b1011;
                break;

                // beqz bnez
              case 0b010101:
              case 0b010110:
                __s = 0b0010;
                break;

                // j
              case 0b100001:
                __s = 0b1000;
                break;

              default:
                __s = 0b1101;
            }
            break;

            /* EXEC I-TYPE */
          case 0b0111:

            // OPERATIONS
            // addi lw sw
            if ((_ir.range(28, 26).to_uint() == 0b001) ||
                (_ir.range(28, 26).to_uint() == 0b011) ||
                (_ir.range(28, 26).to_uint() == 0b100))
              _alu_out = _a.to_int() + _ir.range(15, 0).to_int(); 

            // subi
            else if (_ir.range(28, 26).to_uint() == 0b010)
              _alu_out = _a.to_int() - _ir.range(15, 0).to_int(); 

            // NEXT STATE
            // depends on instruction
            switch (_ir.range(28, 26).to_uint()) {

              /* addi subi */
              case 0b001:
              case 0b010:
              default:
                __s = 0b0101;
                break;

                /* lw */
              case 0b011:
                __s = 0b1111;
                break;

                /* sw */
              case 0b100:
                __s = 0b0110;
            }

            break;

            /* WB I-TYPE */
          case 0b0101:
            _rf[_ir.range(20, 16).to_uint()] = _alu_out;
            __s = 0b0001;
            break;

            /* EXEC R-TYPE */
          case 0b1011:

            // OPERATIONS
            switch (_ir.range(28, 26).to_uint()) {

              /* add */
              case 0b001:
              default:
                _alu_out = _a.to_int() + _b.to_int();
                break;

                /* sub */
              case 0b010:
                _alu_out = _a.to_int() - _b.to_int();
                break;

                /* slt */
              case 0b011:
                _alu_out = (_a.to_int() < _b.to_int()) ? 1 : 0;
                break;

                /* sgt */
              case 0b100:
                _alu_out = (_a.to_int() > _b.to_int()) ? 1 : 0;
            }

            __s = 0b1010;
            break;

            /* WB R-TYPE */
          case 0b1010:
            _rf[_ir.range(15, 11).to_uint()] = _alu_out;
            __s = 0b0001;
            break;

            /* MEM ACCESS R */
          case 0b1111:
            if (cc_rdy.read() == SC_LOGIC_1) {
              _md = cc_dout.read();
              __s = 0b1001;
            }
            break;

            /* WB LOAD */
          case 0b1001:
            _rf[_ir.range(20, 16).to_uint()] = _md;
            __s = 0b0001;
            break;

          /* MEM ACCESS W */
          case 0b0110:
            if (cc_rdy.read() == SC_LOGIC_1)
              __s = 0b0100;
            break;

          /* EXEC BRANCH */
          case 0b0010:
            switch (_ir.range(31,26).to_uint()) {

              case 0b010110: // bnez
                if ((_rf[_ir.range(25, 21).to_uint()].to_uint() - 
                     _rf[_ir.range(20, 16).to_uint()].to_uint()) != 0)
                  _pc = _target;
                break;

              case 0b010101: // beqz
              default:
                if ((_rf[_ir.range(25, 21).to_uint()].to_uint() - 
                     _rf[_ir.range(20, 16).to_uint()].to_uint()) == 0)
                  _pc = _target;
            }

            __s = 0b1101;

            break;

            /* EXEC JUMP */
          case 0b1000:
            _pc.range(31, 28) = pc.read()(31, 28); // this is useless
            _pc.range(27,  2) = ir.read()(25, 0);
            _pc.range( 1,  0) = 0;

            __s = 0b1101;
            break;

            /* REFRESH ADDRESS */
          case 0b1101:
            __s = 0b0001;
            break;
        }

        state.write(__s);
      }
    }

  } // infinite loop

}

void control_unit::fsm_logic(void)
{
  switch (state.read().to_uint()) {
      
    case 0b0001: /* FETCH */
      cc_rw.write(SC_LOGIC_0);
      cc_cs.write(SC_LOGIC_1);
      cc_addr.write(pc.read());
      cc_din.write(0);
      break;

    case 0b1111: /* MEM ACCESS R */
      cc_rw.write(SC_LOGIC_0);
      cc_cs.write(SC_LOGIC_1);
      cc_addr.write(_alu_out);
      cc_din.write(0);
      break;
    
    case 0b0110: /* MEM ACCESS W */
      cc_rw.write(SC_LOGIC_1);
      cc_cs.write(SC_LOGIC_1);
      cc_addr.write(_alu_out);
      cc_din.write(_b);
      break;

    case 0b0000: /* INIT */
    case 0b0011: /* DECODE */
    case 0b0111: /* EXEC I-TYPE */
    case 0b0101: /* WB I-TYPE */
    case 0b1101: /* REFRESH ADDRESS */
    case 0b1001: /* WB LOAD */
    case 0b1011: /* EXEC R-TYPE */
    case 0b1010: /* WB R-TYPE */
    case 0b0010: /* EXEC BRANCH */
    default:
      cc_rw.write(SC_LOGIC_0);
      cc_cs.write(SC_LOGIC_0);
      cc_addr.write(0);
      cc_din.write(0);
  }
}

void control_unit::reg_refresh(void)
{
  pc.write(_pc);
  ir.write(_ir);
  md.write(_md);
  a.write(_a);
  b.write(_b);
  alu_out.write(_alu_out);
  target.write(_target);

  r0.write(_rf[0]);
  r1.write(_rf[1]);
  r2.write(_rf[2]);
  r3.write(_rf[3]);
  r4.write(_rf[4]);
  r5.write(_rf[5]);
}
